/******************************************************************************
CUDA-RT -- a GPU raytracer using CUDA
Copyright (C) 2009 Austin Donisan and Michael Nock

CUDA-RT is free software: you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

CUDA-RT is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with CUDA-RT.  If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "cutil_math.h"
#include "objects.h"
#include "ppm.h"
#include "scene.h"


__global__
void cudaRaytrace(scene *);
void printMessage();
void parseScene(scene *);
void initializeCUDA(int);
void CudaMalloc(void **, size_t);
int newSurface(float3, float3, float3, float, float, float);
void checkErrors(const char *label);
void cudaCopy(void * src, void * dest, int num_objects, size_t size);

int main(int argc, char *argv[])
{
	scene s = {0}, c;
	time_t start_time, parse_time, load_time, trace_time;
	int gpu;
	
	// remmeber what time we started
	time(&start_time);
	
	// print copyright info
	printMessage();
	
	// check for usage
	if(argc < 3)
	{
		fprintf(stderr, "Usage: cuda-rt scene.txt output.ppm (GPU id)\n");
		exit(-1);
	}
	s.infile = argv[1];
	s.outfile = argv[2];
	
	// if they specified a gpu, try to use that one
	if(argc < 4)
		gpu = 0;
	else
		gpu = atoi(argv[3]);
	
	// prepare cuda, make sure they have it
	initializeCUDA(gpu);
	
	// parse scene, time it, and print the results
	printf("Parsing... ");
	fflush(stdout);
	parseScene(&s);
	time(&parse_time);
	printf("done.\n");
	
	printf("Lights    %4d\n", s.n_ls);
	printf("Surfaces  %4d\n", s.n_sfs);
	printf("Triangles %4d\n", s.n_ts);
	printf("Spheres   %4d\n", s.n_ss);
	printf("Planes    %4d\n", s.n_ps);
	printf("Meshes    %4d\n", s.n_ms);
	for(int i = 0; i < s.n_ms; i++)
	{
		printf("%6d Triangles in %s\n", s.m[i].n_ts, s.m[i].file);
		printf("       (%.2f, %.2f, %.2f) to (%.2f, %.2f, %.2f)\n",
		       s.m[i].b.mins.x, s.m[i].b.mins.y, s.m[i].b.mins.z,
		       s.m[i].b.maxs.x, s.m[i].b.maxs.y, s.m[i].b.maxs.z);
	}
	putchar('\n');
	printf("%d %d\n", s.grid_w, s.grid_h);	
	// make a copy of the scene to give to the GPU
	memcpy(&c, &s, sizeof(scene));
	c.m = (f_mesh *) malloc(sizeof(f_mesh) * s.n_ms);
	memcpy(c.m, s.m, sizeof(f_mesh) * s.n_ms);
	
	// put data on gpu
	printf("Loading memory onto GPU... ");
	fflush(stdout);
	
	CudaMalloc((void**) &c.l, s.n_ls * sizeof(light));
	cudaMemcpy(c.l, s.l, s.n_ls * sizeof(light), cudaMemcpyHostToDevice);
	
	for(int i = 0; i < s.n_ms; i++)
	{
		// malloc gpu memory and store pointer in cuda scene struct
		CudaMalloc((void**) &c.m[i].t, s.m[i].n_ts * sizeof(m_triangle));
		
		// copy the memory
		cudaMemcpy(c.m[i].t, s.m[i].t, s.m[i].n_ts * sizeof(m_triangle), cudaMemcpyHostToDevice);
	}
	
	// make room for all the meshes
	void * p_tmp;
	CudaMalloc((void**) &p_tmp, s.n_ms * sizeof(mesh));
	
	// copy the meshes data, and then store the GPU pointer to their data
	cudaCopy((void *) c.m, p_tmp, s.n_ms, sizeof(mesh));
	c.m = (f_mesh *) p_tmp;
	
	// malloc on the gpu and copy all of the triangles, spheres, and planes
	CudaMalloc((void**) &c.t, s.n_ts * sizeof(triangle));
	cudaCopy((void *) s.t, c.t, s.n_ts, sizeof(triangle));
	CudaMalloc((void**) &c.s, s.n_ss * sizeof(sphere));
	cudaCopy((void *) s.s, c.s, s.n_ss, sizeof(sphere));
	CudaMalloc((void**) &c.p, s.n_ps * sizeof(plane));
	cudaCopy((void *) s.p, c.p, s.n_ps, sizeof(plane));
	
	// the actual output
	CudaMalloc((void**) &c.c.out, s.c.x_res * s.c.y_res * sizeof(color));
	
	time(&load_time);
	printf("done.\n\n");
	
	printf("Raytracing %d x %d to %s\n", s.c.x_res, s.c.y_res, s.outfile);
	cudaRaytrace(&c);
	time(&trace_time);
	
	// get the image from the gpu
	cudaMemcpy(s.c.out, c.c.out, s.c.x_res * s.c.y_res * sizeof(color), cudaMemcpyDeviceToHost);
	cudaThreadExit();
	putchar('\n');
	
	checkErrors("copy data from device");
	
  writeppm(s.outfile, s.c.out, s.c.x_res, s.c.y_res);
	
	printf("%ld seconds parsing\n", parse_time - start_time);
	printf("%ld seconds loading\n", load_time  - parse_time);
	printf("%ld seconds tracing\n", trace_time - load_time);
	printf("%ld seconds total\n",   trace_time - start_time);
	return 0;
}

void initializeCUDA(int gpu)
{
	int deviceCount;
	cudaGetDeviceCount(&deviceCount);
	
	if (deviceCount == 0)
	{
		fprintf(stderr, "Sorry, no CUDA device fount");
		exit(-3);
	}
	
	if (gpu >= deviceCount)
	{
		fprintf(stderr, "Choose device ID between 0 and %d\n", deviceCount-1);
		exit(-4);
	}
	
	cudaSetDevice(gpu);
	checkErrors("CUDA initializtion");
}


void checkErrors(const char *label)
{
	cudaError_t err;
	
	err = cudaThreadSynchronize();
	if (err != cudaSuccess)
	{
		char *e = (char*) cudaGetErrorString(err);
		fprintf(stderr, "CUDA Error: %s (at %s)", e, label);
	}
	
	err = cudaGetLastError();
	if (err != cudaSuccess)
	{
		char *e = (char*) cudaGetErrorString(err);
		fprintf(stderr, "CUDA Error: %s (at %s)", e, label);
	}
}

void CudaMalloc(void **p, size_t size)
{
	cudaError_t rc = cudaMalloc(p, size);
	if (rc != cudaSuccess)
	{
		char *e = (char*) cudaGetErrorString(rc);
		fprintf(stderr, "\nCUDA Error: %s\n", e);
		exit(-10);
	}
}

void cudaCopy(void *src, void *dest, int num_objects, size_t size)
{
	for(int i = 0; i < num_objects; i++)
		for(int j = 0; j < size / sizeof(size_t); j++)
		cudaMemcpy(((int *) dest) + num_objects * j + i,
		           ((int *) src) + i * size / sizeof(size_t) + j,
		           sizeof(size_t), cudaMemcpyHostToDevice);
}

void printMessage()
{
	for(int i = 0; i < 80; i++)
		printf("-");
	putchar('\n');
	printf("Copyright (C) 2009 Austin Donisan and Michael Nock\n");
	printf("License LGPLv3+: GNU LGPL version 3 or later <http://gnu.org/licenses/lgpl.html>\n");
	printf("This is free software: you are free to change and redistribute it.\n");
	printf("There is NO WARRANTY, to the extent permitted by law.\n");
	for(int i = 0; i < 80; i++)
		printf("-");
	putchar('\n');
	fflush(stdout);
}
